﻿-- Author      : Kurapica

--------------------------
-- Timer    	        --
--------------------------
--[[
--]]

do
    -- Check Version
	local version = 1

	if not IGAS:RegisterFile("IGAS.GUI.Widget.Timer", version) then
		return
	end
	
    local type = type
    local next = next
    local pairs = pairs
    local select = select
    local tostring = tostring
    local floor = floor
    local max = max
    local setmetatable = setmetatable
	local ipairs = ipairs
	local error = error
	local xpcall = xpcall
    local geterrorhandler = geterrorhandler
	local tinsert = table.insert
	local tremove = table.remove
	
	local CreateFrame = IGAS.GUI.CreateFrame

	local _WidgetName = "Timer"
	local _Base = "VirtualUIObject"
	
	local _GUI = IGAS.GUI
	local _Widget = IGAS.GUI.Widget
	local _BaseFunc = IGAS.GUI.Base.Func
	local _PropertyType = IGAS.GUI.Base.PropertyType

	-- Script
    local _Timer = CreateFrame("Frame", "IGAS_Timer")
	
	local _Container = {}
    _Timer.Container = _Container
	-- used for search
	local _TimerIndex = 1
    local _NowTimer
	
	--[[
		Timers will not be fired more often than HZ-1 times per second.
	--]]
	local HZ = 11
	
	local lastint = floor(GetTime() * HZ)
	
	local function errorhandler(err)
		return geterrorhandler()(err)
	end
	
    local function OnTimer()
		_NowTimer:Fire("OnTimer")
    end
    
	function _Timer:OnUpdate()
		local now = GetTime()
		local nowint = floor(now * HZ)
		
		-- Reduce cpu cost.
		if nowint == lastint then return end
		
		local soon = now + 0.1
	
		-- Consider people will disable timer when a onTimer event triggered, and enable it when all is done, so, I don't use one container to control the enabled timers, another for disabled timers.
		for i, v in ipairs(_Container) do
			if v.__Interval > 0 and v.__FireTime < soon then
                _NowTimer = v
                
				if not xpcall(OnTimer, errorhandler) then
					-- Stop this Timer when error.
					v.__Interval = 0
				else
					-- set next time
					v.__FireTime = now + v.__Interval
				end
			end
		end
		
		lastint = nowint
	end
	
	--[[
		Dispose
	--]]
	local function DisposeTimer(index)
		local timer = _Container[index]
		
		tremove(_Container, index)
		
		-- Call super's dispose
		timer:CallMethod(_Widget[_Base], "Dispose")
	end
	
	local function Dispose(timer, start, last)
		local middle
		
		if start == last then
			if _Container[start] == timer then
				-- Dispose
				DisposeTimer(start)
			end
        elseif (start + 1) == last then
            if _Container[start].__Seed == timer.__Seed then
                DisposeTimer(start)
            elseif _Container[last].__Seed == timer.__Seed then
                DisposeTimer(last)
            end
		else
			middle = floor((start + last) / 2)
			
			if _Container[middle].__Seed == timer.__Seed then
				-- Dispose
				DisposeTimer(middle)
			elseif _Container[middle].__Seed > timer.__Seed then
				Dispose(timer, start, middle)
			else
				Dispose(timer, middle, last)
			end
		end
	end
	
	-- ScriptType
	local _ScriptType = {
		["OnTimer"] = true,
	}
	
	--	FuncProxy
	local _FuncProxy = {
		-- Dispose, release resource
		["Dispose"] = function(self)
			if table.getn(_Container) > 0 then
				Dispose(self, 1, table.getn(_Container))
			end
		end,
	}

	--	Property
	local _Property = {
		-- Interval
		["Interval"] = {
			["Set"] = function(self, int)
				if int and type(int) == "number" and int >= 0 then
					if int == 0 then
						-- Set 0 means stop.
						self.__Interval = 0
					else
						-- Interval must more than 0.1s
						if int < (1 / (HZ - 1)) then
							int = 1 / (HZ - 1)
						end
						
						self.__Interval = int
						-- Restart time count
						self.__FireTime = GetTime() + int
					end
				else
					error("Interval must be a number, and not negative",2)
				end
			end,
			["Get"] = function(self)
				return self.__Interval or 0
			end,
			["Type"] = _PropertyType.Number,
		},
	}

	local function _New(parent)
		local timer = CreateFrame(_Base, nil, parent)
        		
		timer.__Seed = _TimerIndex
		_TimerIndex = _TimerIndex + 1
		
		timer.__Interval = 0
		
		tinsert(_Container, timer)
		
		return timer
	end

	-- Register
	local _WidgetInfo = {
		["WidgetName"] = _WidgetName,
		["Base"] = _Base,
		["FuncProxy"] = _FuncProxy,
		["Property"] = _Property,
		["ScriptType"] = _ScriptType,
		["New"] = _New,
	}

	_Widget[_WidgetName] = _GUI.NewWidget(_WidgetInfo)
end